package util

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/imroc/req"
	jsoniter "github.com/json-iterator/go"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"src/config"
	"strconv"
)

var (
	ErrAppIDNotMatch       = errors.New("app id not match")
	ErrInvalidBlockSize    = errors.New("invalid block size")
	ErrInvalidPKCS7Data    = errors.New("invalid PKCS7 data")
	ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
)

type DecryptUserInfo struct {
	OpenID    string `json:"openId"`   // 注意是openId!
	UnionID   string `json:"unionId"`  // 注意是unionId!
	NickName  string `json:"nickName"` // 注意是nickName!
	Gender    int    `json:"gender"`
	City      string `json:"city"`
	Province  string `json:"province"`
	Country   string `json:"country"`
	AvatarURL string `json:"avatarUrl"`
	Language  string `json:"language"`
	Watermark struct {
		Timestamp int64  `json:"timestamp"`
		AppID     string `json:"appid"`
	} `json:"watermark"`
}

type WeixinMsgSecCheck struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

type WXBizDataCrypt struct {
	appID      string
	sessionKey string
}

func NewWXBizDataCrypt(appID, sessionKey string) *WXBizDataCrypt {
	return &WXBizDataCrypt{
		appID:      appID,
		sessionKey: sessionKey,
	}
}

// pkcs7Unpad returns slice of the original data without padding
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
	if blockSize <= 0 {
		return nil, ErrInvalidBlockSize
	}
	if len(data)%blockSize != 0 || len(data) == 0 {
		return nil, ErrInvalidPKCS7Data
	}
	c := data[len(data)-1]
	n := int(c)

	if n == 0 || n > len(data) {
		return nil, ErrInvalidPKCS7Padding
	}

	return data[:len(data)-n], nil
}

func (w *WXBizDataCrypt) Decrypt(encryptedData, iv string) (*DecryptUserInfo, error) {
	aesKey, err := base64.StdEncoding.DecodeString(w.sessionKey)
	if err != nil {
		return nil, err
	}
	cipherText, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, err
	}
	ivBytes, err := base64.StdEncoding.DecodeString(iv)
	if err != nil {
		return nil, err
	}
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	mode := cipher.NewCBCDecrypter(block, ivBytes)
	mode.CryptBlocks(cipherText, cipherText)
	cipherText, err = pkcs7Unpad(cipherText, block.BlockSize())
	if err != nil {
		return nil, err
	}

	var userInfo DecryptUserInfo
	err = json.Unmarshal(cipherText, &userInfo)
	if err != nil {
		return nil, err
	}
	if userInfo.Watermark.AppID != w.appID {
		return nil, ErrAppIDNotMatch
	}
	return &userInfo, nil
}

// PostWeixinMsgSecCheck 敏感内容检查API
func PostWeixinMsgSecCheck(accessToken, content string) (bool, error) {
	ret := WeixinMsgSecCheck{}

	param := req.Param{
		"access_token": accessToken,
	}

	request := map[string]string{
		"content": content,
	}

	r, err := req.Post(config.WeixinMsgSecCheckURI, param, req.BodyJSON(request))
	if err != nil {
		return false, err
	}

	err = r.ToJSON(&ret)
	if err != nil {
		return false, err
	}
	if ret.ErrCode == 87014 {
		return false, nil
	}
	if ret.ErrCode != 0 {
		return false, errors.New("PostWeixinMsgSecCheck failed: " + strconv.Itoa(ret.ErrCode) + " " + ret.ErrMsg)
	}
	return true, nil
}

// postWeixinImgSecCheck 敏感图片检查API
// 不知道为什么使用req框架失败
func postWeixinImgSecCheck(accessToken string, file multipart.File, fileName string) (bool, error) {
	ret := WeixinMsgSecCheck{}

	buf := new(bytes.Buffer)
	w := multipart.NewWriter(buf)
	fw, _ := w.CreateFormFile("media", fileName) // 这里的uploadFile必须和服务器端的FormFile-name一致
	_, _ = io.Copy(fw, file)
	_ = w.Close()

	resp, err := http.Post(fmt.Sprintf("%s?access_token=%s", config.WeixinImgSecCheckURI, accessToken),
		w.FormDataContentType(), buf)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("ioutil.ReadAll Error", err)
	}
	err = jsoniter.Unmarshal(body, &ret)
	if err != nil {
		return false, err
	}
	if ret.ErrCode == 87014 {
		return false, nil
	}
	if ret.ErrCode != 0 {
		return false, errors.New("PostWeixinImgSecCheck failed: " + strconv.Itoa(ret.ErrCode) + " " + ret.ErrMsg)
	}
	return true, nil
}
